#pragma once

#include <iostream>
#include <vector>
#include <cassert>
#include <semaphore.h>
#include <pthread.h>

static const int gcap = 4;

// 环形队列 -- 用模板后续可以传入任务
template <class T>
class RingQueue
{
private:
    // 信号量需要保证原子性 P/V操作
    void P(sem_t &sem)
    {
        int n = sem_wait(&sem); // 信号量--
        assert(n == 0);
        (void)n;
    }
    void V(sem_t &sem)
    {
        int n = sem_post(&sem); // 信号量++
        assert(n == 0);
        (void)n;
    }
    // sem_wait和sem_post 申请不到的时候将线程阻塞等待直到能申请再唤醒

public:
    // 队列容量空间为cap 容量大小为cap
    RingQueue(const int &cap = gcap) : _queue(cap), _cap(cap)
    {
        // 初始化信号量
        int n = sem_init(&_spaceSem, 0, _cap); // 空间资源一开始为cap
        assert(n == 0);
        n = sem_init(&_dataSem, 0, 0); // 数据资源一开始为0
        assert(n == 0);

        _productorStep = _consumerStep = 0; // 一开始生产者和消费者指向同一位置

        pthread_mutex_init(&_pmutex, nullptr);
        pthread_mutex_init(&_cmutex, nullptr);
    }
    // 放入数据 -- 生产者
    void Push(const T &in)
    {
        P(_spaceSem);                  // 申请空间信号量 申请成功可以进行正常生产 失败阻塞等待
        pthread_mutex_lock(&_pmutex);  // 加锁 - 确保放入操作不出现问题
        _queue[_productorStep++] = in; // 后置++
        _productorStep %= _cap;
        pthread_mutex_unlock(&_pmutex);
        V(_dataSem); // 释放数据信号量 此时有数据了 可以取数了

        // 关键点: 应该先加锁还是先申请信号量？
        // 答案: 先申请信号量
        // 只要拿到信号量表示还有资源可以放入/取出，线程先申请信号量然后进行排队等锁
        // 如果先加锁，那么当线程拿到锁又要去申请信号量，效率低
    }

    // 取出数据 -- 消费者
    void Pop(T *out)
    {
        P(_dataSem); // 申请数据信号量 有数据才能拿
        pthread_mutex_lock(&_cmutex);
        *out = _queue[_consumerStep++]; // 将数据取出 ，然后++
        _consumerStep %= _cap;
        pthread_mutex_unlock(&_cmutex);
        V(_spaceSem);
    }

    ~RingQueue()
    {
        // 销毁信号量
        sem_destroy(&_spaceSem);
        sem_destroy(&_dataSem);
        // 销毁锁
        pthread_mutex_destroy(&_pmutex);
        pthread_mutex_destroy(&_cmutex);
    }

private:
    std::vector<T> _queue;
    int _cap; // 容量大小

    // 定义两个信号量
    sem_t _spaceSem; // 生产者看重的是空间资源
    sem_t _dataSem;  // 消费者看重的是数据资源

    int _productorStep;
    int _consumerStep;

    pthread_mutex_t _pmutex; // 生产者锁
    pthread_mutex_t _cmutex; // 消费者锁
};